﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;

namespace SimpleIR
{
	/// <summary>
	/// This class is used to represent all bytecode operations that take a scalar value on the eval stack and evaluate to a new value on the eval stack.
	/// </summary>
	public sealed class IRPointerIndexOp : IRByteCode
	{
		/// <summary>
		/// An array containing all the acceptable opcodes for this operation class (in order)
		/// </summary>
		public static readonly OpCode.Op[] AcceptableOps = { OpCode.Op.LoadLocalAddr, OpCode.Op.LoadGlobalAddr, OpCode.Op.IndexRefAddrByField, OpCode.Op.IndexRefAddrByArrayIndex };

		/// <summary>
		/// If this is a load local address this is the name of the local var.
		/// </summary>
		private readonly IRVarName m_var;

		/// <summary>
		/// If this is a load global address this is the name of the static field.
		/// </summary>
		private readonly IRStaticField m_sfld;

		/// <summary>
		/// If this is a index by member field operation this is the field to index to.
		/// </summary>
		private readonly IRMemberField m_fieldindex;

		/// <summary>
		/// If this is a index by array offset this is the type of the container to index on.
		/// </summary>
		private readonly IRContainerType m_basecontainertype;

		/// <summary>
		/// This if for field/array ptr indexing, it contains the stack index where the pointer we want to index is stored.
		/// </summary>
		private readonly int m_stackptrindex;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant((this.OperationCode != OpCode.Op.LoadLocalAddr) | (this.m_var != null));
			Contract.Invariant((this.OperationCode != OpCode.Op.LoadGlobalAddr) | (this.m_sfld != null));
			Contract.Invariant((this.OperationCode != OpCode.Op.IndexRefAddrByField) | (this.m_fieldindex != null));
			Contract.Invariant((this.OperationCode != OpCode.Op.IndexRefAddrByArrayIndex) | (this.m_basecontainertype != null));
		}

		private IRPointerIndexOp(OpCode.Op opc, IRVarName var, IRStaticField sfld, IRMemberField mfld, IRContainerType cty, int sidx, IRSourceLocation sloc)
			: base(opc, sloc)
		{
			Contract.Requires(AcceptableOps.Contains(opc));

			this.m_var = var;
			this.m_sfld = sfld;
			this.m_fieldindex = mfld;
			this.m_basecontainertype = cty;
			this.m_stackptrindex = sidx;
		}

		/// <summary>
		/// Make the bytecode for loading the address of the given local variable.
		/// </summary>
		public static IRPointerIndexOp MakeLoadLocalAddr(IRVarName v, IRSourceLocation sloc)
		{ return new IRPointerIndexOp(OpCode.Op.LoadLocalAddr, v, null, null, null, -1, sloc); }

		/// <summary>
		/// Make the bytecode for loading the address of the given global variable.
		/// </summary>
		public static IRPointerIndexOp MakeLoadGlobalAddr(IRStaticField sfld, IRSourceLocation sloc)
		{ return new IRPointerIndexOp(OpCode.Op.LoadGlobalAddr, null, sfld, null, null, -1, sloc); }

		/// <summary>
		/// Make the bytecode indexing the pointer at stack[top-sloc] with the given field.
		/// </summary>
		public static IRPointerIndexOp MakeFieldAddrIndex(IRMemberField fld, int sloc, IRSourceLocation srcloc)
		{ return new IRPointerIndexOp(OpCode.Op.IndexRefAddrByField, null, null, fld, null, sloc, srcloc); }

		/// <summary>
		/// Make the bytecode indexing the pointer at stack[top-sloc] with an index on the stack.
		/// </summary>
		public static IRPointerIndexOp MakeArrayAddrIndex(IRContainerType arraytype, int sloc, IRSourceLocation srcloc)
		{ return new IRPointerIndexOp(OpCode.Op.IndexRefAddrByArrayIndex, null, null, null, arraytype, sloc, srcloc); }

		public override String ToString()
		{
			if(this.OperationCode == OpCode.Op.LoadLocalAddr)
				return "varaddr:    of " + this.m_var.ToString();
			else if(this.OperationCode == OpCode.Op.LoadGlobalAddr)
				return "staddr:     of " + this.m_sfld.ToString();
			else if(this.OperationCode == OpCode.Op.IndexRefAddrByField)
				return "indexptr:  fld " + "@ " + this.m_stackptrindex + " " + this.m_fieldindex.ToString();
			else if(this.OperationCode == OpCode.Op.IndexRefAddrByArrayIndex)
				return "indexptr: base " + "@ " + this.m_stackptrindex + " " + this.m_basecontainertype.ToString();
			else
				return "--BAD OPCODE--";
		}

		/// <summary>
		/// If this is an load local address operation, this returns the var to get the address of.
		/// </summary>
		public IRVarName LocalName
		{
			get
			{
				Contract.Requires(this.OperationCode == OpCode.Op.LoadLocalAddr);
				Contract.Ensures(Contract.Result<IRVarName>() != null);

				return this.m_var;
			}
		}

		/// <summary>
		/// If this is an load global address operation, this returns the static field to get the address of.
		/// </summary>
		public IRStaticField StaticField
		{
			get
			{
				Contract.Requires(this.OperationCode == OpCode.Op.LoadGlobalAddr);
				Contract.Ensures(Contract.Result<IRStaticField>() != null);

				return this.m_sfld;
			}
		}

		/// <summary>
		/// If this is an field index operation, this returns the member field.
		/// </summary>
		public IRMemberField IndexField
		{
			get
			{
				Contract.Requires(this.OperationCode == OpCode.Op.IndexRefAddrByField);
				Contract.Ensures(Contract.Result<IRMemberField>() != null);

				return this.m_fieldindex;
			}
		}

		/// <summary>
		/// If this is an array index operation, this returns the type of the underlying array.
		/// </summary>
		public IRContainerType ArrayType
		{
			get
			{
				Contract.Requires(this.OperationCode == OpCode.Op.IndexRefAddrByArrayIndex);
				Contract.Ensures(Contract.Result<IRContainerType>() != null);

				return this.m_basecontainertype;
			}
		}

		/// <summary>
		/// This is the index (top is 0) of the de-refable (pointer or reference) that we want to do the indexing on.
		/// </summary>
		public int StackIndexOfDeRefable
		{ get { return this.m_stackptrindex; } }
	}
}
